using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;

using Patterns.Tools.CodeGenerators.GoogleCode.Sidebar;
using Patterns.Tools.Properties;

namespace Patterns.Tools.CodeGenerators.GoogleCode
{
	public static class Wiki
	{
		private const string _debugOutputFormat = @"bin\Debug\{0}.{1}";
		private static readonly Func<string, string, string> _debugOutput = (s1, s2) => string.Format(_debugOutputFormat, s1, s2);

		private static readonly Func<string, string, string, FileInfo> _debugFile
			= (s1, s2, s3) => new FileInfo(Path.Combine(s1, _debugOutput(s2, s3)));

		public static CodeGenerationOutput Generate(Options options)
		{
			if (string.IsNullOrEmpty(options.Output)) throw new ArgumentException("Output path is required for Google Wiki generation.");
			if (string.IsNullOrEmpty(options.WikiUri)) throw new ArgumentException(Resources.WikiUriRequired);

			string sidebarName = string.IsNullOrEmpty(options.WikiSideBarName) ? "Index" : options.WikiSideBarName;

			string filter = string.IsNullOrEmpty(options.ProjectFilter) ? "Patterns*.csproj" : options.ProjectFilter;
			ScanResults[] results = ScanAssemblies(filter, options.WikiUri, options.SourceUri, sidebarName).ToArray();
			
			string fullOutputPath = Path.GetFullPath(options.Output);
			string outputDirectory = Path.HasExtension(fullOutputPath) ? Path.GetDirectoryName(fullOutputPath) : fullOutputPath;
			var resultDirectory = new DirectoryInfo(outputDirectory);
			if(!resultDirectory.Exists) resultDirectory.Create();

			foreach (ScanResults result in results)
			{
				string resultFile = Path.Combine(resultDirectory.FullName, string.Format("{0}.wiki", result.Name));
				File.WriteAllText(resultFile, result.Content);
				Console.WriteLine(resultFile);
			}

			string sidebarFile = Path.Combine(resultDirectory.FullName, string.Format("{0}.wiki", sidebarName));
			File.WriteAllText(sidebarFile, GetSideBar(sidebarName, results, options.WikiUri));
			Console.WriteLine(sidebarFile);

			return new CodeGenerationOutput { IsCodeGenerationHandled = true };
		}

		private static IEnumerable<ScanResults> ScanAssemblies(string projectFilter, string wikiUri, string sourceUri, string sidebarName)
		{
			var dir = new DirectoryInfo(Environment.CurrentDirectory);

			var projectScan = dir.GetFiles(projectFilter, SearchOption.AllDirectories)
				.Select(item => new { Directory = item.Directory.FullName, Name = Path.GetFileNameWithoutExtension(item.FullName) })
				.Select(item => new { item.Name, Dll = _debugFile(item.Directory, item.Name, "dll"), Xml = _debugFile(item.Directory, item.Name, "xml") })
				.ToArray();

			var invalidProjects = projectScan.Where(item => !item.Dll.Exists || !item.Xml.Exists).ToArray();

			if (invalidProjects.Any())
			{
				var oldColor = Console.ForegroundColor;
				Console.ForegroundColor = ConsoleColor.Red;
				foreach (var project in invalidProjects)
				{
					Console.WriteLine(@"Could not generate wiki output for {0}: was expecting a .DLL file and a .XML file in the bin\Debug folder...", project.Name);
					Console.WriteLine();
				}
				Console.ForegroundColor = oldColor;
			}

			var targets = projectScan.Where(item => item.Dll.Exists && item.Xml.Exists).ToArray();

			if(!targets.Any()) yield break;

			using (var scope = new AppDomainScope())
			{
				var scanner = scope.CreateRemoteObject<Scanner>();
				IEnumerable<ScanResults> results = targets
					.SelectMany(item => scanner.ScanAssembly(new ScanRequest(item.Dll.FullName, item.Xml.FullName, wikiUri, sourceUri, sidebarName)));
				foreach (ScanResults result in results) yield return result;
			}
		}

		private static string GetSideBar(string sidebarName, IEnumerable<ScanResults> results, string wikiUri)
		{
			var allResults = results.ToArray();
			return new GoogleWikiSidebar
			{
				Name = sidebarName,
				WikiUri = wikiUri,
				Items = allResults
					.Where(item => item.Name.StartsWith("assembly_"))
					.Select(result => new SidebarAnalysis { Result = result, Children = GetChildren(result, allResults) }),
			}.TransformText();
		}

		private static IEnumerable<SidebarAnalysis> GetChildren(ScanResults result, ScanResults[] allResults)
		{
			return allResults
				.Except(new[] { result })
				.Where(item => item.ParentName == result.Name)
				.Select(scanResult => new SidebarAnalysis { Result = scanResult, Children = GetChildren(scanResult, allResults) });
		}
	}
}